package com.ease.map.handler.mouse {
    import com.ease.map.Map;
    import com.ease.map.feature.Feature;
    import com.ease.map.geometry.IGeometry;
    import com.ease.map.geometry.PolygonGeometry;
    import com.ease.map.geometry.basetype.Extent;
    import com.ease.map.geometry.basetype.Location;
    import com.ease.map.geometry.basetype.Pixel;
    import com.ease.map.handler.Handler;
    import com.ease.map.layer.FeatureLayer;
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.events.TimerEvent;
    import flash.geom.Rectangle;
    import flash.utils.Timer;

    /**
     * 鼠标点击地图操作
     */
    public class ClickHandler extends Handler {
        /**
         * Callback function click(p:Pixel,features:Vector.<Feature>,evt:MouseEvent):void
         * This function is called after a MouseUp event (in the case of a
         * simple click)
         */
        private var _click:Function = null;

        /**
         * Callback function doubleClick(p:Pixel,features:Vector.<Feature>,evt:MouseEvent):void
         * This function is called after a MouseUp event (in the case of a
         * double click)
         */
        private var _doubleClick:Function = null;

        /**
         * Callback function drag(evt:MouseEvent):void
         * This function is called during a MouseMove event (in the case of a
         * drag&drop click) ; the function is not called at the MouseDown time.
         */
        private var _drag:Function = null;

        /**
         * Callback function drop(p:Pixel,features:Vector.<Feature>,evt:MouseEvent):void
         * This function is called after a MouseUp event (in the case of a
         * drag&drop click)
         */
        private var _drop:Function = null;

        /**
         * Pixel under the cursor when the mouse is down
         */
        protected var _downPixel:Pixel = null;

        /**
         *  Pixel under the cursor when the mouse is up
         */
        protected var _upPixel:Pixel = null;

        /**
         * Timer used to detect a double click without throwing a simple click
         */
        private var _timer:Timer = new Timer(200, 1);

        /**
         * Number of click since the beginning of the timer.
         * It is used to decide if the user has done a simple or a double click.
         */
        private var _clickNum:Number = 0;

        /**
         * CTRL is pressed ?
         */
        protected var _ctrlKey:Boolean = false;

        /**
         * SHIFT is pressed ?
         */
        protected var _shiftKey:Boolean = false;

        protected var _dragging:Boolean = false;

        protected var _mouseEvent:MouseEvent = null;

        /**
         * Size in pixels of the selection buffer (default=2 so a point is a
         * 5-side square)
         */
        private var _selectionBuffer:Number = 2;

        /**
         * Constructor of the handler.
         * @param map the map associated to the handler
         * @param active boolean defining if the handler is active or not
         */
        public function ClickHandler(map:Map, handlername:String) {
            super(map, handlername);
        }

        /**
         * Click function getter and setter
         */
        public function get click():Function {
            return this._click;
        }

        public function set click(value:Function):void {
            this._click = value;
        }

        /**
         * Double click function getter and setter
         */
        public function get doubleClick():Function {
            return this._doubleClick;
        }

        public function set doubleClick(value:Function):void {
            this._doubleClick = value;
        }

        /**
         * Drag function getter and setter
         */
        public function get drag():Function {
            return this._drag;
        }

        public function set drag(value:Function):void {
            this._drag = value;
        }

        /**
         * Drop function getter and setter
         */
        public function get drop():Function {
            return this._drop;
        }

        public function set drop(value:Function):void {
            this._drop = value;
        }

        /**
         * Map coordinates (in its baselayer's SRS) of the point clicked (at the
         * beginning of the drag)
         */
        protected function startCoordinates():Location {
            return (this.map && this._downPixel) ? this.map.getLocationFromMapPx(this._downPixel) : null;
        }

        /**
         * The select box, in pixels, defining by the pixel clicked at the
         * beginning of the drag (mouseDown) and the pixel where the mouseUp
         * event occurs.
         *
         * @param evt the MouseEvent that defines the second point of the box
         * @param buffer the buffer, in pixels, to use to enlarge the selection
         * box (useful to improve the ergonomy)
         */
        protected function selectionBoxPixels(p:Pixel, buffer:Number = 0):Rectangle {
            if (!this._downPixel) {
                return null;
            }
            var left:Number = Math.min(this._downPixel.x, p.x) - buffer;
            var top:Number = Math.min(this._downPixel.y, p.y) - buffer;
            var w:Number = Math.abs(this._downPixel.x - p.x) + 2 * buffer;
            var h:Number = Math.abs(this._downPixel.y - p.y) + 2 * buffer;
            return new Rectangle(left, top, w, h);
        }

        /**
         * The select box, in map's coordinates, defining by the point clicked
         * at the beginning of the drag (mouseDown) and the point where the
         * mouseUp event occurs.
         * This function calls selectBoxPixels and convert the Rectangle of
         * pixels in a  Bounds of map's coordinates.
         *
         * @param evt the MouseEvent that defines the second point of the box
         * @param buffer the buffer, in pixels (not in coordinates), to use to
         * enlarge the selection box (useful to improve the ergonomy)
         * */
        protected function selectionBoxCoordinates(p:Pixel, buffer:Number = 0):Extent {
            var rect:Rectangle = this.selectionBoxPixels(p, buffer);
            if ((!rect) || (!this.map)) {
                return null;
            }
            var bottomLeft:Location = this.map.getLocationFromMapPx(new Pixel(rect.left, rect.bottom));
            var topRight:Location = this.map.getLocationFromMapPx(new Pixel(rect.right, rect.top));
            return new Extent(bottomLeft.lon, bottomLeft.lat, topRight.lon, topRight.lat, map.projection);
        }

        /**
         * get all the displayed features that intersect the input geometry.
         */
        protected function selectByGeometry(geom:IGeometry):Vector.<Feature> {
            // Look for all the features that intersect the selection geometry
            var featuresToSelect:Vector.<Feature> = new Vector.<Feature>();
            if (geom) {
                var layer:FeatureLayer;
                // Remove invisible layers from the list of selectable layers
                for each (layer in this.map.featureLayers) {
                    if (layer.displayed && layer.selectable) {
                        for each (var feature:Feature in layer.features) {
                            if ((geom as PolygonGeometry).intersects(feature.geometry)) {
                                featuresToSelect.push(feature);
                            }
                        }
                    }
                }
                //点击feature后查看这个feature在不在这个空间分析出的设施列表里，如果不在则加入
                if (this._mouseEvent.target is Feature) {
                    if (featuresToSelect.indexOf(this._mouseEvent.target) < 0) {
                        if (this._mouseEvent.target is Feature) {
                            if (((this._mouseEvent.target as Feature).layer as FeatureLayer).selectable) {
                                featuresToSelect.push(this._mouseEvent.target);
                            }
                        }
                    }
                }

                if (this._mouseEvent.target is Sprite) {
                    if (this._mouseEvent.target.parent is Feature) {
                        if (featuresToSelect.indexOf(this._mouseEvent.target.parent as Feature) < 0) {
                            if (((this._mouseEvent.target.parent as Feature).layer as FeatureLayer).selectable) {
                                featuresToSelect.push(this._mouseEvent.target.parent as Feature);
                            }
                        }
                    }
                }

            }

            return featuresToSelect;

        }

        protected function selectByPixel(p:Pixel):Vector.<Feature> {
            // Get the selection area
            var sbox:Extent = this.selectionBoxCoordinates(p, this.selectionBuffer);
            var sboxGeom:IGeometry = (sbox) ? sbox.toGeometry() : null;

            // Select the features that intersect the geometry
            return this.selectByGeometry(sboxGeom);
        }

        /**
         * Selection buffer getter and setter
         */
        public function get selectionBuffer():Number {
            return this._selectionBuffer;
        }

        public function set selectionBuffer(value:Number):void {
            this._selectionBuffer = Math.max(0, value);
        }


        /**
         * Add the listeners to the associated map
         */
        override protected function registerListeners():void {
            // Listeners of the super class
            super.registerListeners();
            // Listeners of the internal timer
            this._timer.addEventListener(TimerEvent.TIMER, useRightCallback);
            // Listeners of the associated map
            if (this.map) {
                this.map.addEventListener(MouseEvent.MOUSE_DOWN, this.mouseDown);
                this.map.addEventListener(MouseEvent.MOUSE_UP, this.mouseUp);
            }
        }

        /**
         * Remove the listeners to the associated map
         */
        override protected function unregisterListeners():void {
            // Listeners of the associated map
            if (this.map) {
                this.map.removeEventListener(MouseEvent.MOUSE_DOWN, this.mouseDown);
                this.map.removeEventListener(MouseEvent.MOUSE_MOVE, this.mouseMove);
                this.map.removeEventListener(MouseEvent.MOUSE_UP, this.mouseUp);
            }
            this._downPixel = null;
            this._upPixel = null;
            this._ctrlKey = false;
            this._shiftKey = false;
            this._dragging = false;
            // Listeners of the internal timer
            this._timer.removeEventListener(TimerEvent.TIMER, useRightCallback);
            this._timer.stop();
            this._clickNum = 0;
            // Listeners of the super class
            super.unregisterListeners();
        }

        /**
         * The MouseDown Listener
         * @param evt the MouseEvent
         */
        protected function mouseDown(evt:MouseEvent):void {
            if (evt) {
                this._downPixel = new Pixel(evt.currentTarget.mouseX, evt.currentTarget.mouseY);
                this.map.addEventListener(MouseEvent.MOUSE_MOVE, this.mouseMove);
            }
        }

        /**
         * The MouseMove Listener
         * @param evt the MouseEvent
         */
        protected function mouseMove(evt:MouseEvent):void {
            if (evt) {
                this._dragging = true;
                if (this.drag != null) {
                    // Use the callback function for a drag click
                    this.drag(evt);
                }
            }
        }

        /**
         * MouseUp Listener
         * @param evt the MouseEvent
         */
        protected function mouseUp(evt:MouseEvent):void {
            if (evt) {
                this.map.removeEventListener(MouseEvent.MOUSE_MOVE, this.mouseMove);

                if (this._downPixel != null) {
                    // It was not a drag, but was it a simple or a double click ?
                    // Just wait for a timer duration to know and call the right function.
                    this._upPixel = new Pixel(evt.currentTarget.mouseX, evt.currentTarget.mouseY);
                    this._mouseEvent = evt;
                    this._ctrlKey = evt.ctrlKey;
                    this._shiftKey = evt.shiftKey;
                    this._clickNum++;
                    this._timer.start();
                }
            }
        }

        /**
         * Define if there was a double click or a simple click (drag&drop is
         * managed before if needed).
         * @param evt the TimerEvent (not used)
         */
        private function useRightCallback(evt:TimerEvent):void {
            if (this._dragging) {
                this._dragging = false;
                if (this.drop != null) {
                    // Use the callback function for a drop click
                    this.drop(this._upPixel, this.selectByPixel(this._upPixel), this._mouseEvent);
                }
            }
            else if (this._clickNum == 1) {
                if (this.click != null) {
                    // Use the callback function for a simple click
                    this.click(this._upPixel, this.selectByPixel(this._upPixel), this._mouseEvent);
                }
            }
            else if (this.doubleClick != null) {
                // Use the callback function for a double click
                this.doubleClick(this._upPixel, this.selectByPixel(this._upPixel), this._mouseEvent);
            }

            this._timer.stop();
            this._clickNum = 0;
            this._downPixel = null;
            this._upPixel = null;
            this._ctrlKey = false;
            this._shiftKey = false;
        }

    }
}
